
import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;

import msu.cse.turtlegraphics.*;

import java.awt.Point;
import java.io.InputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class Interpreter {
    
    CommonTree root;               // the AST represents our code memory
    TokenRewriteStream tokens;
    LogoTurtleLexer lex;              // lexer/parser are part of the processor
    LogoTurtleParser parser;
    MemorySpace globalSpace = new MemorySpace("global");
    List<MemorySpace> scopes = new LinkedList<MemorySpace>();
    MemorySpace functions = new MemorySpace("functions");   
    Turtle turtle = new Turtle();
	TurtleDisplayFrame frame = new TurtleDisplayFrame();
	int intVal = LogoTurtleParser.INT;
	int floatVal = LogoTurtleParser.FLOAT;
    
    
    
    
    
    public void interp(InputStream input) throws RecognitionException, IOException {
        lex = new LogoTurtleLexer(new ANTLRInputStream(input));
        tokens = new TokenRewriteStream(lex);
        parser = new LogoTurtleParser(tokens);

    	frame.setVisible(true);
    	turtle.setCurrentTurtleDisplayCanvas(frame.getCurrentCanvas());       
        
        scopes.add(0, globalSpace);

        LogoTurtleParser.prog_return r = parser.prog();
        if ( parser.getNumberOfSyntaxErrors()==0 ) {
            root = (CommonTree)r.getTree();
            block(root);
        }
    }

    /** visitor dispatch according to node token type */
    public Object exec(CommonTree t) {
        try {
            switch ( t.getType() ) {
            case LogoTurtleParser.BLOCK : block(t); break;
            case LogoTurtleParser.MAKE : make(t); break;
            case LogoTurtleParser.PRINT : print(t); break;
            case LogoTurtleParser.IFSTAT : ifstat(t); break;
            case LogoTurtleParser.IFELSE : ifelse(t); break;
            case LogoTurtleParser.WHILESTAT : whilestat(t); break;
            case LogoTurtleParser.COMPOP : break;
            case LogoTurtleParser.TO : funcmake(t); break;
            case LogoTurtleParser.PENDOWN : turtPendown(t); break;
            case LogoTurtleParser.PENUP :   turtPenup(t); break;
            case LogoTurtleParser.FORWARD : turtForward(t); break;
            case LogoTurtleParser.BACKWARD : turtBackward(t); break;
            case LogoTurtleParser.LEFT : turtLeft(t); break;
            case LogoTurtleParser.RIGHT : turtRight(t); break;
            case LogoTurtleParser.SETH : turtSethead(t); break;
            case LogoTurtleParser.SETPOS : turtPosition(t); break;
            case LogoTurtleParser.CIRCLE : turtCircle(t); break;
            case LogoTurtleParser.SETPEN : turtSetpen(t); break;
            case LogoTurtleParser.BFILL : turtBfill(t); break;
            case LogoTurtleParser.EFILL : turtEfill(t); break;
            case LogoTurtleParser.REFOP	: return getVar(t.getChild(0).toString(), scopes);
            case LogoTurtleParser.ADD	: return add(t);
            case LogoTurtleParser.SUB	: return sub(t);
            case LogoTurtleParser.MUL	: return mul(t);
            case LogoTurtleParser.MOD	: return mod(t);
            case LogoTurtleParser.DIV 	: return div(t);
            case LogoTurtleParser.INT	: return Integer.parseInt(t.getText());
            case LogoTurtleParser.FLOAT : return Float.parseFloat(t.getText());
            case LogoTurtleParser.ID 	: return runFunc(t); 
            
            }
        }
        catch (Exception e) {
	    System.out.println("Caught an error in the switch");
        }
        return null;
    }

    public void block(CommonTree t) {

    	List<CommonTree> stats = t.getChildren();
    	for (CommonTree x : stats) {
    		exec(x);
    	}
    }


    public void print(CommonTree t) {
        List<CommonTree> prints = t.getChildren();
        
        
        for(CommonTree var : prints){        

        	System.out.println( exec(var).toString() );
        	
        }

    }

    public void whilestat(CommonTree t) {
    	
 	   if( compare( (CommonTree)t.getChild(0))){	
		   block( t );
		   whilestat(t);
	   } 
 	   
    }

   public void ifelse(CommonTree t) {
	   if( compare( (CommonTree) t.getChild(0))){		   
		   block( (CommonTree) t.getChild(1) );
	   } else {
		   block( (CommonTree) t.getChild(2) );		   
	   }
    }

   public void make(CommonTree t) {
        CommonTree var = (CommonTree) t.getChild(0);
          	
        	scopes.get(0).put(var.getChild(0).toString(), exec( (CommonTree) t.getChild(1) ));         
        
    }

   public void ifstat(CommonTree t) {	   
	   if( compare( (CommonTree)t.getChild(0))){		   
		   block( t );
	   } 
    }
   
   
   public boolean compare(CommonTree t){
	   boolean not = false;
	   boolean compare = false;
	   Object left;
	   Object right;
	   CommonTree tmp = t;
	   
	   if( t.toString().compareTo("not") == 0 ){
		  tmp = (CommonTree) t.getChild(0);
		  not = true;
	   }
	   
	   left = exec((CommonTree)tmp.getChild(0));
	   right = exec((CommonTree)tmp.getChild(1));
	   double rightVal = Double.parseDouble(right.toString());
	   double leftVal = Double.parseDouble(left.toString());
	   
	   if( tmp.toString().compareTo("=") == 0 ){
		   if(rightVal == leftVal){
			   compare = true;
		   } else compare = false;
	   }else if( tmp.toString().compareTo("<=") == 0 ){
		   if(leftVal <= rightVal){
			   compare = true;
		   } else compare = false;	   
	   }else if( tmp.toString().compareTo("<") == 0 ){
		   if(leftVal < rightVal){
			   compare = true;
		   } else compare = false;			   
	   }else if( tmp.toString().compareTo(">") == 0 ){
		   if(leftVal > rightVal){
			   compare = true;
		   } else compare = false;		   
	   }else if( tmp.toString().compareTo(">=") == 0 ){
		   if(leftVal >= rightVal){
			   compare = true;
		   } else compare = false;
	   }
	   
	   if(not && compare) compare = false;
	   else if(not) compare = true;	   
	   
	   return compare;
   }
   
   public Object getVar(String ID, List<MemorySpace> list){  
	   
	   
	   for(MemorySpace space : list){
		   if( space.get(ID) != null){		   
			   return space.get(ID);
		   }
	   }
	   
	   return ID;
   }
   
   public void funcmake(CommonTree t){	 	   
	   	   
	   functions.put(t.getChild(0).toString(), t.getChild(0)); 
	   
   }
   
   
   
   public Object runFunc(CommonTree t){
	   
	   Object retObject = null;
	   MemorySpace localSpace = new MemorySpace(t.toString());
	   scopes.add( 0, localSpace);
	   CommonTree funcTree = (CommonTree) functions.get(t.toString());
	   
	   List<CommonTree> args = t.getChildren();
	   List<CommonTree> funcs = funcTree.getChildren();
	   
	  
	   
	   int index = 0;
	   for(CommonTree x : args){
		   scopes.get( 0 ).put( funcTree.getChild(index).getChild(0).toString() , exec(x));		   
		   index++;	   
	   }
	   
	   
	   


	   for (CommonTree x : funcs) {
		   
	       switch ( x.getType() ) {
	       case LogoTurtleParser.OUTPUT	: retObject = exec( (CommonTree)x.getChild(0)); break;
	       case LogoTurtleParser.REFOP 	: break;
	       default						: exec(x);
	       }
	   }
	   
	   scopes.remove(0);	   
	   
	   return retObject;
   }
   
   
   public Object add(CommonTree t){
	
	   Number leftNum;
	   Number rightNum;
	   
	   
	   leftNum = (Number) exec( (CommonTree) t.getChild(0) );
	   rightNum = (Number) exec( (CommonTree) t.getChild(1) );
	   
	   if( leftNum instanceof Integer && rightNum instanceof Integer){
		   return leftNum.intValue() + rightNum.intValue();			   
	   } else if(leftNum instanceof Float && rightNum instanceof Float ){
		   return leftNum.floatValue() + rightNum.floatValue();			   
	   } else{
		   System.out.println("Variables not of matching type");
		   return leftNum.floatValue() + rightNum.floatValue();	
	   }	  
	   
   }
   
   public Object sub(CommonTree t){
	   

	   Number leftNum;
	   Number rightNum;
	   
	   
	   leftNum = (Number) exec( (CommonTree) t.getChild(0) );
	   rightNum = (Number) exec( (CommonTree) t.getChild(1) );
	   
	   if( leftNum instanceof Integer && rightNum instanceof Integer){
		   return leftNum.intValue() - rightNum.intValue();			   
	   } else if(leftNum instanceof Float && rightNum instanceof Float ){
		   return leftNum.floatValue() - rightNum.floatValue();			   
	   } else{
		   System.out.println("Variables not of matching type");
		   return leftNum.floatValue() - rightNum.floatValue();	
	   }
	   
   }
   
   public Object mul(CommonTree t){
	   

	   Number leftNum;
	   Number rightNum;
	   
	   
	   leftNum = (Number) exec( (CommonTree) t.getChild(0) );
	   rightNum = (Number) exec( (CommonTree) t.getChild(1) );
	   
	   if( leftNum instanceof Integer && rightNum instanceof Integer){
		   return leftNum.intValue() * rightNum.intValue();			   
	   } else if(leftNum instanceof Float && rightNum instanceof Float ){
		   return leftNum.floatValue() * rightNum.floatValue();			   
	   } else{
		   System.out.println("Variables not of matching type");
		   return leftNum.floatValue() * rightNum.floatValue();	
	   }
	   
   }
   
   public Object div(CommonTree t){
	   

	   Number leftNum;
	   Number rightNum;
	   
	   
	   leftNum = (Number) exec( (CommonTree) t.getChild(0) );
	   rightNum = (Number) exec( (CommonTree) t.getChild(1) );
	   
	   if( leftNum instanceof Integer && rightNum instanceof Integer){
		   return leftNum.intValue() / rightNum.intValue();			   
	   } else if(leftNum instanceof Float && rightNum instanceof Float ){
		   return leftNum.floatValue() / rightNum.floatValue();			   
	   } else{
		   System.out.println("Variables not of matching type");
		   return leftNum.floatValue() / rightNum.floatValue();	
	   }
   }
   
   public Object mod(CommonTree t){
	   

	   Number leftNum;
	   Number rightNum;
	   
	   
	   leftNum = (Number) exec( (CommonTree) t.getChild(0) );
	   rightNum = (Number) exec( (CommonTree) t.getChild(1) );
	   
	   if( leftNum instanceof Integer && rightNum instanceof Integer){
		   return leftNum.intValue() % rightNum.intValue();			   
	   } else if(leftNum instanceof Float && rightNum instanceof Float ){
		   return leftNum.floatValue() % rightNum.floatValue();			   
	   } else{
		   System.out.println("Variables not of matching type");
		   return leftNum.floatValue() % rightNum.floatValue();	
	   }
	   
   }
   
   
   public void turtPendown(CommonTree t){
	   
	   	   turtle.turtlePenDown();
	   
   }
   
   
   public void turtPenup(CommonTree t){
	   
	   turtle.turtlePenUp();
	   
   }
   
   
   public void turtForward(CommonTree t){
	   
	   turtle.turtleForward( Integer.parseInt(exec( (CommonTree) t.getChild(0) ).toString() ) );	   
	   
   }
   
   
   public void turtBackward(CommonTree t){
	   
	   turtle.turtleBackward( Integer.parseInt( exec( (CommonTree) t.getChild(0) ).toString() ) );
	   
   }
   
   
   public void turtLeft(CommonTree t){
	   
	   turtle.turtleLeft( Integer.parseInt( exec( (CommonTree) t.getChild(0) ).toString() ) );	   
	   
   }
   
   
   public void turtRight(CommonTree t){
	   
	   turtle.turtleRight( Integer.parseInt( exec( (CommonTree) t.getChild(0) ).toString() ) );
	   
   }
   
   
   public void turtSethead(CommonTree t){
	   
	   turtle.turtleSetHeading( Double.parseDouble( exec( (CommonTree) t.getChild(0) ).toString() ) );

   }
   
   
   public void turtPosition(CommonTree t){
	   
	   turtle.turtleGoto( new Point(
			   Integer.parseInt(exec( (CommonTree) t.getChild(0) ).toString()),
			   Integer.parseInt(exec( (CommonTree) t.getChild(1) ).toString())
	   ));	   
	   
   }
   
   
   public void turtCircle(CommonTree t){
	   
	   turtle.turtleCircle(
			   Integer.parseInt(exec( (CommonTree) t.getChild(0) ).toString()),
			   Integer.parseInt(exec( (CommonTree) t.getChild(1) ).toString())	   
	   );	   
	   
   }
   
   
   public void turtSetpen(CommonTree t){
	   
	   turtle.turtleSetColor(
			   Integer.parseInt(exec( (CommonTree) t.getChild(0) ).toString()),
			   Integer.parseInt(exec( (CommonTree) t.getChild(1) ).toString()),
			   Integer.parseInt(exec( (CommonTree) t.getChild(2) ).toString())
	   );	   
	   
   }
   
   
   public void turtBfill(CommonTree t){
	   
	   turtle.turtleBeginFillPolygon();	   	   
	   
   }


   public void turtEfill(CommonTree t){
	   
	   turtle.turtleEndFillPolygon();
	   
   }   

}
